﻿using Silk.NET.Maths;
using Silk.NET.Windowing;
using Silk.NET.Windowing.Extensions;
using Silk.NET.Input;
using Silk.NET.Input.Glfw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using GLES = Silk.NET.OpenGLES;


namespace OpenGLLib;

public sealed unsafe class SilkOpenGLESUtils
{
    private IWindow win;
    private IInputContext inputCtx;
    private GLES.GL gl;
    private uint width;
    private uint height;
    private uint shaderProgram;
    private int ySamplerLoc;
    private int uvSamplerLoc;
    private uint yTextureId;
    private uint uvTextureId;

    public void CreateWin(uint width = 0u, uint height = 0u, string title = null)
    {
        width = this.width = width < 1 ? 1280u : width;
        height = this.height = height < 1 ? 720u : height;
        win = SilkOpenGLESUtilsExtensions.CreateNativeWin((int)this.width, (int)this.height, title);
        if (win != null)
        {
            win.Load += OnLoad;
            win.Update += OnUpdate;
            win.Render += OnRender;

            win.Initialize();
            gl = GLES.GL.GetApi(win);
            InitGLES(gl);

            //InitShaderProgram(out shaderProgram, out ySamplerLoc, out uvSamplerLoc);
            yTextureId = gl.GenTexture();
            uvTextureId = gl.GenTexture();
        }
    }

    private void OnLoad()
    {
        inputCtx = win.CreateInput();
        for (int i = 0; i < inputCtx.Keyboards.Count; i++)
        {
            inputCtx.Keyboards[i].KeyDown += KeyDown;
        }
    }

    private void KeyDown(IKeyboard arg1, Key arg2, int arg3)
    {
        if (arg2 == Key.Escape)
        {
            win.Close();
        }
    }

    public void Show() => win.Run();

    public void Close() => win.Close();

    public void InitShaderProgram(out uint shaderProgram, out int ySamplerLoc, out int uvSamplerLoc)
    {
        uvSamplerLoc = ySamplerLoc = 0;
        shaderProgram = 0;

        string vShaderStr = @"
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main()
{
gl_Position = a_position;
v_texCoord = a_texCoord;
}";
        string fShaderStr = @"
#version 300 es
precision mediump float;
in vec2 v_texCoord;                            
layout(location = 0) out vec4 outColor;
uniform sampler2D y_texture;
uniform sampler2D uv_texture;
void main()
{                                         
vec3 yuv;
yuv.x = texture(y_texture, v_texCoord).r;
yuv.y = texture(uv_texture, v_texCoord).a-0.5;
yuv.z = texture(uv_texture, v_texCoord).r-0.5;
vec3 rgb =mat3( 1.0,       1.0,           1.0,
                0.0,         -0.344,     1.770,
                1.403,  -0.714,       0.0) * yuv;
outColor = vec4(rgb, 1);
}";
        //string y_texture = "y_texture";
        //string uv_texture = "uv_texture";
        //IntPtr yPtr = Marshal.StringToHGlobalAnsi(y_texture);
        //IntPtr uvPtr = Marshal.StringToHGlobalAnsi(uv_texture);
        //shaderProgram = gl.CreateShaderProgram(GLES.GLEnum.Shader, 2, new string[] { vShaderStr, fShaderStr });
        //ySamplerLoc = gl.GetUniformLocation(shaderProgram, (byte*)(yPtr.ToPointer()));
        //uvSamplerLoc = gl.GetUniformLocation(shaderProgram, (byte*)(uvPtr.ToPointer()));

        uint mainId = gl.CreateProgram();
        uint vertex = gl.CreateShader(GLES.GLEnum.VertexShader);
        gl.ShaderSource(vertex, vShaderStr);
        gl.AttachShader(mainId, vertex);

        uint fragment = gl.CreateShader(GLES.GLEnum.FragmentShader);
        gl.ShaderSource(fragment, fShaderStr);
        gl.AttachShader(mainId, fragment);

        gl.LinkProgram(mainId);
        gl.GetProgram(mainId, GLES.GLEnum.LinkStatus, out int success);
        if (success < 1)
        {
            string log = gl.GetProgramInfoLog(mainId);
            throw new Exception(log);
        }
        gl.DeleteShader(vertex);
        gl.DeleteShader(fragment);
    }

    public unsafe void UpdateYuv(byte*[] ptrs, int[] lines) // int width = 0, int height = 0
    {
        if (shaderProgram == 0 || ySamplerLoc == 0 || uvSamplerLoc == 0)
        {
            return;
        }
        byte* yAddr = ptrs[0];
        byte* uAddr = ptrs[1];
        byte* vAddr = ptrs[2];

        //upload Y plane data
        gl.BindTexture(GLES.GLEnum.Texture2D, yTextureId);
        gl.TexImage2D(GLES.GLEnum.Texture2D, 0, (int)GLES.GLEnum.Luminance, width, height,
            0, GLES.GLEnum.Luminance, GLES.GLEnum.UnsignedByte, yAddr);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapT, (int)GLES.GLEnum.ClampToEdge);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMinFilter, (int)GLES.GLEnum.Linear);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMagFilter, (int)GLES.GLEnum.Linear);
        gl.BindTexture(GLES.GLEnum.Texture2D, (int)GLES.GLEnum.None);

        //upload UV plane data
        gl.BindTexture(GLES.GLEnum.Texture2D, uvTextureId);
        gl.TexImage2D(GLES.GLEnum.Texture2D, 0, (int)GLES.GLEnum.LuminanceAlpha, width, height,
            0, GLES.GLEnum.LuminanceAlpha, GLES.GLEnum.UnsignedByte, uAddr);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapT, (int)GLES.GLEnum.ClampToEdge);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMinFilter, (int)GLES.GLEnum.Linear);
        gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMagFilter, (int)GLES.GLEnum.Linear);
        gl.BindTexture(GLES.GLEnum.Texture2D, (int)GLES.GLEnum.None);

        float[] verticesCoords = {
            -1.0f,  0.78f, 0.0f,  // Position 0
            -1.0f, -0.78f, 0.0f,  // Position 1
            1.0f,  -0.78f, 0.0f,  // Position 2
            1.0f,   0.78f, 0.0f,  // Position 3
        };

        float[] textureCoords = {
            0.0f,  0.0f,        // TexCoord 0
            0.0f,  1.0f,        // TexCoord 1
            1.0f,  1.0f,        // TexCoord 2
            1.0f,  0.0f         // TexCoord 3
        };
        ushort[] indices = { 0, 1, 2, 0, 2, 3 };
        gl.UseProgram(shaderProgram);

        fixed (void* prt = verticesCoords)
            gl.VertexAttribPointer(0, 3, GLES.GLEnum.Float, false, 12u, prt);

        fixed (void* prt = textureCoords)
            gl.VertexAttribPointer(1, 2, GLES.GLEnum.Float, false, 8u, prt);

        gl.EnableVertexAttribArray(0);
        gl.EnableVertexAttribArray(1);

        // Bind the Y plane map
        gl.ActiveTexture(GLES.GLEnum.Texture0);
        gl.BindTexture(GLES.GLEnum.Texture2D, yTextureId);
        // Set the Y plane sampler to texture unit to 0
        gl.Uniform1(ySamplerLoc, 0);

        // Bind the UV plane map
        gl.ActiveTexture(GLES.GLEnum.Texture1);
        gl.BindTexture(GLES.GLEnum.Texture2D, uvTextureId);
        // Set the UV plane sampler to texture unit to 0
        gl.Uniform1(uvSamplerLoc, 0);

        fixed (void* prt = indices)
            gl.DrawElements(GLES.GLEnum.Triangles, 6, GLES.GLEnum.UnsignedShort, prt);
    }


    private void OnUpdate(double ptr)
    {

    }

    private void OnRender(double ptr)
    {

    }

    #region static
    public static void InitGLES(GLES.GL gl)
    {
        string shader = @"
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
    gl_Position = vertexIn;
    textureOut = textureIn;
}";
        //uint shaderPtr = gl.CreateShader(GLES.GLEnum.ShaderStorageBuffer);
        //gl.ShaderSource(shaderPtr, shader);
        //uint textureY = gl.GenTexture();
        //uint textureU = gl.GenTexture();
        //uint textureV = gl.GenTexture();
        //gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
    }

    #endregion
}

public class SilkOpenGLESUtilsExtensions
{
    public static Silk.NET.Windowing.IWindow CreateNativeWin(int width, int height, string title = null)
    {
        WindowOptions options = new WindowOptions
        (
           true,
           new Vector2D<int>(50, 50),
           new Vector2D<int>(width, height),
           25d,
           25d,
           new GraphicsAPI
           (
               ContextAPI.OpenGL,
               ContextProfile.Core,
               ContextFlags.ForwardCompatible,
               new APIVersion(3, 3)
           ),
           title,
           Silk.NET.Windowing.WindowState.Normal,
           WindowBorder.Resizable,
           false,
           false,
           VideoMode.Default
        );
        return Silk.NET.Windowing.Window.Create(options);
    }

}